package display.client;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import com.google.gwt.i18n.client.NumberFormat;

import track.ObjectLocation;
import track.PointTrack;
import track.Pos;

public class SensorData {
    // Map of information on published PointTrack instances.
    private static HashMap<Integer, PointTrackInfo> m_published = new HashMap<Integer, PointTrackInfo> ();

    private static Vector<Integer> disposedIds = new Vector<Integer>();
    
    public static String m_padding;

    static class PointTrackInfo {
        private PointTrack m_pointTrack;

        private boolean m_disposed = false;
    }

    public static void disposePointTrackWithId(Integer id) {
        //System.out.println ("SensorData dispose object " + id);
        m_published.remove(id);
    }
    
    private static void disposeAndUnregister(PointTrackInfo info) {
        // Dispose and unregister from the appropriate data writer.
        if (info.m_disposed == false) {
            // Remove from the map.
            Integer key = new Integer(info.m_pointTrack.trackId);
            Object oldData = m_published.remove(key);
            if (oldData == null) {
                System.out.println("Error: could not find map entry during disposal/unregister for key "
                        + key);
            } else {
                IdentificationData.disposeTrackStateWithId(info.m_pointTrack.trackId);
                disposedIds.add(info.m_pointTrack.trackId);
            }            
        }
    }
    
    public static void updateObjects () {
        // Process the data if scanning is in operation.
        ObjectLocation[] locs = BattlefieldData.getObjectLocationsWithoutUpdate();
        int count = 0;

        for (int i = 0; i < locs.length; i++) {
            ObjectLocation loc = locs[i];
            Integer mapKey = new Integer(loc.trackId);
            PointTrackInfo info = (PointTrackInfo) m_published.get(mapKey);

            float distance = calcDistance(Constants.SENSOR_X,
                    Constants.SENSOR_Y, Constants.SENSOR_Z, loc.x,
                    loc.y, loc.z);
            if (distance > Constants.SENSOR_RANGE) {
                // Object has moved out of sensor range; dispose and
                // unregister the instance. Strictly the
                // dispose/unregister on PointTrack should result from a
                // dispose/unregister on ObjectLocation,
                // but doing this means objects get "stuck" on the
                // display for the interval between leaving
                // the sensor range and leaving the battlefield.
                // Disposing/unregistering at this point removes
                // the display, but relies on the knowledge that objects
                // travel in a straight line and so once
                // out of scope range will leave the battlefield without
                // returning to within range.
                if (info != null) {
                    disposeAndUnregister(info);
                }
            } else {
                // Object is within sensor range; publish the
                // information to the PointTrack topic
                // via the appropriate writer depending on altitude. We
                // synchronise on the writer
                // so that strength and priority changes aren't done at
                // the same time.

                count++;

                // Create and register a PointTrack instance if needed.
                if (info == null) {
                    info = new PointTrackInfo();
                    info.m_pointTrack = new PointTrack();
                    info.m_pointTrack.trackId = loc.trackId;
                    info.m_pointTrack.padding = m_padding;
                    m_published.put(mapKey, info);
                }

                // Update the position information and publish.
                info.m_pointTrack.position = new Pos(distance,
                        calcDirection(Constants.SENSOR_X,
                                      Constants.SENSOR_Y, loc.x, loc.y),
                        calcAngle(Constants.SENSOR_X,
                                  Constants.SENSOR_Y, Constants.SENSOR_Z,
                                  loc.x, loc.y, loc.z));

                StringBuffer sb = new StringBuffer();
                sb.append(new Date());
                sb.append(" -- ");
                sb.append("ID: ");
                sb.append(info.m_pointTrack.trackId);
                sb.append(", DIST: ");
                sb.append(NumberFormat.getFormat("#,##0.00")
                        .format(info.m_pointTrack.position.distance));
                sb.append(", DIR: ");
                sb.append(NumberFormat.getFormat("#,##0.00")
                        .format(info.m_pointTrack.position.direction));
                sb.append(", ANG: ");
                sb.append(NumberFormat.getFormat("#,##0.00")
                        .format(info.m_pointTrack.position.angle));
                System.out.println (sb.toString());
            }
        }
    }

    /**
     * Calculates the flat-plane direction between two points.
     */
    private static float calcDirection(int x1, int y1, int x2, int y2) {
        int delta_y = y2 - y1;
        int delta_x = x2 - x1;
        float res = (float) Math.toDegrees(Math.atan((double) delta_y
                / delta_x));

        if (delta_x < 0) {
            res = 270 + res;
        } else if (delta_x > 0) {
            res = 90 + res;
        } else {
            if (delta_y < 0) {
                res = 0;
            } else {
                res = 180;
            }
        }

        return res;
    }

    /**
     * Calculates the distance between two points.
     */
    private static float calcDistance(int x1, int y1, int z1, int x2, int y2,
            int z2) {
        float adjSq = (float) (Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
        float oppSq = (float) Math.pow(z2 - z1, 2);

        float distance = (float) Math.sqrt(adjSq + oppSq);

        return distance;
    }

    /**
     * Calculates the angle of elevation between two points.
     */
    private static float calcAngle(int x1, int y1, int z1, int x2, int y2, int z2) {
        float adj = (float) Math.sqrt(Math.pow(x2 - x1, 2)
                + Math.pow(y2 - y1, 2));
        float opp = (float) (z2 - z1);

        float angle = (float) Math.toDegrees(Math.atan((double) opp / adj));

        return angle;
    }
    
    public static PointTrack[] getPointTracks () {
        updateObjects ();
        PointTrack[] pts = new PointTrack[m_published.size()];
        int index = 0;
        synchronized (m_published) {
            Set<Integer> keys = m_published.keySet();
            Iterator<Integer> keysIter = keys.iterator();
            while (keysIter.hasNext()) {
                Integer key = (Integer) keysIter.next();
                PointTrackInfo tracked = (PointTrackInfo) m_published.get(key);
                pts[index] = tracked.m_pointTrack;
                index++;
            }
        }
        return pts;
    }

    public static PointTrack[] getPointTracksWithoutUpdate() {
        PointTrack[] pts = new PointTrack[m_published.size()];
        int index = 0;
        synchronized (m_published) {
            Set<Integer> keys = m_published.keySet();
            Iterator<Integer> keysIter = keys.iterator();
            while (keysIter.hasNext()) {
                Integer key = (Integer) keysIter.next();
                PointTrackInfo tracked = (PointTrackInfo) m_published.get(key);
                pts[index] = tracked.m_pointTrack;
                index++;
            }
        }
        return pts;
    }

    public static Integer[] getDisposedPointTrackIds() {
        Integer[] ids = new Integer[disposedIds.size()];
        ids = disposedIds.toArray(ids);
        disposedIds.clear();
        return ids;
    }
}